package stmlib;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;

/** Static class that provides global Stm helper functions.  @author Seun Osewa */

public class Stm {
    /** Tracks the number of data retries since the beginning of this JVM run. */
    public static AtomicLong retries = new AtomicLong(0);

    static class TransactionData {
        final HashSet<LockManager> locks = new HashSet<LockManager>();
        final ArrayList<Runnable> undoLog = new ArrayList<Runnable>();
        final ArrayList<Runnable> runOnCommit = new ArrayList<Runnable>();
        
        TransactionData parent;
        public TransactionData (TransactionData parent) { this.parent = parent; }

        boolean active = false;  //transaction in progress?

        void begin() {
            if (this.active) {
                //create subtransaction
                final TransactionData td2 = new TransactionData(this);
                td2.active = true;
                data.set(td2); //transfer control to subtransaction
            } else {
                this.active = true;
            }
        }

        void commit() {
            if (parent == null) {
                for(LockManager lm: locks){ lm.unlockAll(); } // free all locks
                for (Runnable r: runOnCommit) { r.run(); } // post-transaction commands
                clear();
            } else { //has a parent
                for(LockManager lm: locks){ parent.locks.add(lm); } //delegate locks to parent.
                parent.runOnCommit.addAll(runOnCommit);     //delegate onCOmmits to parent
                parent.undoLog.add(new Runnable(){public void run(){
                    for(int j = undoLog.size() - 1; j >= 0; j--) undoLog.get(j).run();
                }}); //so the parent transaction can undo this transaction if necessary
                data.set(parent); //return control to the parent transaction.
            }
            this.active = false;
        }

        void rollback() {
            //undo all changes the current transaction has made:
            for(int j = undoLog.size() - 1; j >= 0; j--) undoLog.get(j).run();

            if(parent == null) { // has no parent? release all locks:
                for(LockManager lm: locks){ lm.unlockAll(); }
                clear();
            } else {             //has parent: transfer control of locks to parent:
                for(LockManager lm: locks){ parent.locks.add(lm); }
                data.set(parent); //return control to the parent transaction.
            }
            this.active = false;
        }

        /** clear all logs, forget about all locks. */
        final void clear() { locks.clear(); runOnCommit.clear(); undoLog.clear(); }
    }

    static ThreadLocal<TransactionData> data = 
        new ThreadLocal<TransactionData>() {
            protected TransactionData initialValue() { return new TransactionData(null); }
        };

    /** This function is called once for each object that the current data must track. */
    public static void register(LockManager lm) {
        final TransactionData td = data.get();
        if (td.active) td.locks.add(lm); else throw new Exceptions.NotInTransaction();
    }

    /** for debugging, testing, and internal use only.  Supports nested transactions. */
    public static void begin() { data.get().begin(); }

    /** for debugging, testing, and internal use only.  Supports nested transactions. */
    public static void commit() { data.get().commit(); }
                
    /** for debugging, testing, and internal use only.  Supports nested transactions. */
    public static void rollback() { data.get().rollback(); }

    static void logUndo(Runnable action) { data.get().undoLog.add(action); }

    /** Run the provided runnable if the current transaction is successful. */
    public static void onCommit(Runnable r) {
        final TransactionData td = data.get();
        if (td.active) td.runOnCommit.add(r); else throw new Exceptions.NotInTransaction();
    }

    /** Print this string or Object if the current transaction is successful */
    public static void println(final Object output) {
        onCommit(new Runnable(){public void run(){
            System.out.println(output);
        }});
    }
}